summaryrefslogtreecommitdiff
path: root/app/[lng]/admin/edp/components/vendor-selector.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/admin/edp/components/vendor-selector.tsx')
-rw-r--r--app/[lng]/admin/edp/components/vendor-selector.tsx281
1 files changed, 281 insertions, 0 deletions
diff --git a/app/[lng]/admin/edp/components/vendor-selector.tsx b/app/[lng]/admin/edp/components/vendor-selector.tsx
new file mode 100644
index 00000000..22dfad65
--- /dev/null
+++ b/app/[lng]/admin/edp/components/vendor-selector.tsx
@@ -0,0 +1,281 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { Button } from '@/components/ui/button'
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Badge } from '@/components/ui/badge'
+import { Search, Check } from 'lucide-react'
+import {
+ ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+ SortingState,
+ ColumnFiltersState,
+ VisibilityState,
+ RowSelectionState,
+} from '@tanstack/react-table'
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table'
+import { getVendors } from '../actions/data-actions'
+import { toast } from 'sonner'
+
+interface Vendor {
+ id: number
+ vendorName: string
+ vendorCode: string | null
+ status: string
+}
+
+interface VendorSelectorProps {
+ selectedVendor?: Vendor
+ onVendorSelect: (vendor: Vendor) => void
+ disabled?: boolean
+}
+
+export function VendorSelector({ selectedVendor, onVendorSelect, disabled }: VendorSelectorProps) {
+ const [open, setOpen] = useState(false)
+ const [vendors, setVendors] = useState<Vendor[]>([])
+ const [loading, setLoading] = useState(false)
+ const [sorting, setSorting] = useState<SortingState>([])
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
+ const [globalFilter, setGlobalFilter] = useState('')
+
+ const columns: ColumnDef<Vendor>[] = [
+ {
+ accessorKey: 'vendorName',
+ header: '벤더명',
+ cell: ({ row }) => (
+ <div className="font-medium">{row.getValue('vendorName')}</div>
+ ),
+ },
+ {
+ accessorKey: 'vendorCode',
+ header: '벤더 코드',
+ cell: ({ row }) => {
+ const code = row.getValue('vendorCode') as string | null
+ return code ? (
+ <div className="font-mono text-sm">{code}</div>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ )
+ },
+ },
+ {
+ accessorKey: 'status',
+ header: '상태',
+ cell: ({ row }) => {
+ const status = row.getValue('status') as string
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'ACTIVE': return 'bg-green-100 text-green-800'
+ case 'APPROVED': return 'bg-blue-100 text-blue-800'
+ case 'PENDING_REVIEW': return 'bg-yellow-100 text-yellow-800'
+ case 'INACTIVE': return 'bg-gray-100 text-gray-800'
+ default: return 'bg-gray-100 text-gray-800'
+ }
+ }
+ return (
+ <Badge className={getStatusColor(status)}>
+ {status}
+ </Badge>
+ )
+ },
+ },
+ {
+ id: 'actions',
+ header: '선택',
+ cell: ({ row }) => (
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => handleVendorSelect(row.original)}
+ >
+ <Check className="h-4 w-4" />
+ </Button>
+ ),
+ },
+ ]
+
+ const table = useReactTable({
+ data: vendors,
+ columns,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ onColumnVisibilityChange: setColumnVisibility,
+ onRowSelectionChange: setRowSelection,
+ onGlobalFilterChange: setGlobalFilter,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ state: {
+ sorting,
+ columnFilters,
+ columnVisibility,
+ rowSelection,
+ globalFilter,
+ },
+ })
+
+ const loadVendors = async () => {
+ setLoading(true)
+ try {
+ const result = await getVendors()
+ if (result.success) {
+ setVendors(result.data)
+ } else {
+ toast.error(result.error)
+ }
+ } catch (error) {
+ toast.error('벤더를 불러오는 중 오류가 발생했습니다.')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleVendorSelect = (vendor: Vendor) => {
+ onVendorSelect(vendor)
+ setOpen(false)
+ }
+
+ useEffect(() => {
+ if (open && vendors.length === 0) {
+ loadVendors()
+ }
+ }, [open])
+
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button variant="outline" disabled={disabled} className="w-full justify-start">
+ {selectedVendor ? (
+ <div className="flex items-center gap-2">
+ <span className="truncate">{selectedVendor.vendorName}</span>
+ {selectedVendor.vendorCode && (
+ <span className="font-mono text-sm text-muted-foreground">
+ ({selectedVendor.vendorCode})
+ </span>
+ )}
+ </div>
+ ) : (
+ <span className="text-muted-foreground">벤더를 선택하세요</span>
+ )}
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-4xl max-h-[80vh]">
+ <DialogHeader>
+ <DialogTitle>벤더 선택</DialogTitle>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ <div className="flex items-center space-x-2">
+ <Search className="h-4 w-4" />
+ <Input
+ placeholder="벤더명, 벤더 코드로 검색..."
+ value={globalFilter}
+ onChange={(e) => setGlobalFilter(e.target.value)}
+ className="flex-1"
+ />
+ </div>
+
+ {loading ? (
+ <div className="flex justify-center py-8">
+ <div className="text-sm text-muted-foreground">벤더를 불러오는 중...</div>
+ </div>
+ ) : (
+ <div className="border rounded-md">
+ <Table>
+ <TableHeader>
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id}>
+ {headerGroup.headers.map((header) => (
+ <TableHead key={header.id}>
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ ))}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ <TableRow
+ key={row.id}
+ data-state={row.getIsSelected() && "selected"}
+ className="cursor-pointer hover:bg-muted/50"
+ onClick={() => handleVendorSelect(row.original)}
+ >
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id}>
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell
+ colSpan={columns.length}
+ className="h-24 text-center"
+ >
+ 검색 결과가 없습니다.
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ )}
+
+ <div className="flex items-center justify-between">
+ <div className="text-sm text-muted-foreground">
+ 총 {table.getFilteredRowModel().rows.length}개 벤더
+ </div>
+ <div className="flex items-center space-x-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ 이전
+ </Button>
+ <div className="text-sm">
+ {table.getState().pagination.pageIndex + 1} / {table.getPageCount()}
+ </div>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ 다음
+ </Button>
+ </div>
+ </div>
+ </div>
+ </DialogContent>
+ </Dialog>
+ )
+}